/*

数据包格式: 55 AA len(2B) seq(2B) cmd(1B) payload(N) crcH crcL
len: 大端字节序模式，表示整个payload部分数据包的(不包含crc字段)，不包含len本身，目前只使用最低12bit, 高4bit保留(最大单帧长度2048)
seq: 大端字节序模式，表示数据包的序号，用于数据包的重传,目前只使用低12bit,

定义cmd:
cmd: 的最高bit表示是否为应答包，0表示不是应答包，1表示是应答包
当cmd为0x00时，表示心跳数据包，对方收到后应该回复一个心跳数据包，并回复同样的应答包
当cmd: 0x01: 表示准备发送文件: payload的格式如下
    fmt(1B): 0: jpg, 1: png, 2: bmp
    filesize(3B):
    
    //响应数据
    code: 0: 已经准备好接受, 1: 不支持的格式， 2: 文件过大

0x02: 表示发送的数据，每个数据包都要收到响应，否则报错
    code: 0: 数据正常, 1: 数据错误

0x03: 表示发送完成，表示发送完成，对方收到后应该回复一个应答包，并回复同样的应答包
    code: 0: 数据正常, 1: 数据错误

*/

export default class DataChannel {
	static crc16Table = [];

	// 初始化 CRC16 表 
	static initializeCrc16Table() {
		for (let i = 0; i < 256; i++) {
			let crc = i;
			for (let j = 0; j < 8; j++) {
				crc = (crc & 0x01) ? (0xA001 ^ (crc >> 1)) : (crc >> 1);
			}
			DataChannel.crc16Table[i] = crc;
		}
	}

	constructor(sendfunc) {
		this.sendfunc = sendfunc;
		this.recvdataBuf = [];
		this.senddataBuf = [];
		this.senddataOffset = 0; //发送数据的偏移
		this.sendBlockSize = 490; //256 480->491  500
		this.sendBinHandler = null;
		this.blcokTimeoutTimr = null;
		this.maxRetryInUnit = 5
		this.curfileSendCallback = null;
		this.curSendSeq = 1;
		this.blockSendTimes = 0;
		this.blcokResendTimeout = 1000;
		this.curfileFormat = 0;
		this.isBusySending = 0;
		DataChannel.initializeCrc16Table();
	}

	//实现一个modbus crc16算法
	crc16(data) {
		let crc = 0xFFFF;
		for (let i = 0; i < data.length; i++) {
			crc = (crc >> 8) ^ DataChannel.crc16Table[(crc ^ data[i]) & 0xFF];
		}
		return crc;
	}

	increaseSeq() {
		this.curSendSeq++; //序列号范围限制在: 1-0xFFF0
		if (this.curSendSeq > 0xFFF0) {
			this.curSendSeq = 1;
		}
		return this.curSendSeq
	}

	onRecvData(indata, recvcb) {
		//判断recvdata是否为byte数组类型，如果不是报错

		let recvdata = new Uint8Array(indata);

		console.log("-----------onRecvData-------------")
		if (this.senddataBuf.byteLength == 0) {
			//当前没有发送数据，啥也不做，以后可能会有其他处理
			console.log("no file in sending")
			return
		}

		//解析当前的包结构，根据前面注释的定义，包最小9个字节
		if (recvdata.byteLength < 9) {
			//打印出来是数据包不合适的错误
			console.log("invalid recv data length:", recvdata.byteLength)
			return;
		}

		//验证首字节是否为0x55 0xAA
		if (recvdata[0] != 0x55 || recvdata[1] != 0xAA) {
			//不是合法的数据包, 在当前的位置，我继续发当前的数据
			console.log("invalid packet header.")
			return;
		}

		//获取数据包的长度
		let len = (recvdata[2] << 8) | recvdata[3];
		if (len != recvdata.byteLength - 9) {
			//数据包长度不合适
			console.log("invalid packet byteLength:", recvdata.byteLength, " frmlen:", len)
			return;
		}

		//获取cmd
		let cmd = recvdata[6];
		//首先通过最高bit判断是否为返回包
		let isAckPacket = (cmd & 0x80) != 0;
		if (isAckPacket) {
			//获取seq
			let seq = (recvdata[4] << 8) | recvdata[5];
			if (seq != this.curSendSeq) {
				//数据包序号不合适，不是我的数据包，不要处理
				return;
			}
		} else {
			//暂时不支持非ACk包，直接返回
			console.log("not support non ack packet");
			return;
		}

		//收到响应就不要重发
		if (this.blcokTimeoutTimr != null) {
			clearTimeout(this.blcokTimeoutTimr);
		}
		this.blcokTimeoutTimr = null;
		//cmd的低7位
		cmd = cmd & 0x7F;
		if (cmd == 0x00) { //响应
			console.log("recv ack packet");
			this.increaseSeq();
			this.isBusySending = 0;
		} else if (cmd == 0x01) { //判断发送文件是否准备好了
			let filestart_ack = recvdata[7];
			console.log("filestart_ack:", filestart_ack);
			if (filestart_ack == 0) {
				//文件已经准备好了，可以发送数据了
				if (this.curfileSendCallback != null) {
					this.curfileSendCallback(0, 0, "start");
				}
				//这个地方判断数据是否发送完成
				if (this.senddataOffset == 0) {
					//开始正式进入发送了
					this.blockSendTimes = 0;
					//增加序列号, 重置序列号为初始值:
					this.increaseSeq();
					this.sendBlockData();
				} //大于0的情况可能是上一个同样的序列号我已经处理过了

			} else if (filestart_ack == 1) {
				this.abortTransFile(100, -1, "not support format");
			} else if (filestart_ack == 2) {
				this.abortTransFile(100, -2, "too large file");
			} else {
				this.abortTransFile(100, -3, "unknown error");
			}
		} else if (cmd == 0x02) { //文件数据发送中
			//获取响应码
			let code = recvdata[7];
			if (code == 0) {
				//数据正常
				this.blockSendTimes = 0;
				//序列号增加
				this.increaseSeq();
				//加上一个block的偏移
				this.senddataOffset += this.sendBlockSize;
				//计算发送的percent
				let percent = Math.floor(this.senddataOffset / this.senddataBuf.byteLength * 100);
				//percent转为证书，最大不超过99
				percent = percent > 99 ? 99 : percent;
				if (this.senddataOffset >= this.senddataBuf.byteLength) {
					this.senddataOffset = this.senddataBuf.byteLength;
				}
				//同步更新比例及文件大小
				this.curfileSendCallback(percent, this.senddataOffset, "sending");
				if (this.senddataOffset >= this.senddataBuf.byteLength) {
					this.senddataOffset = this.senddataBuf.byteLength;
					//数据发送完成
					this.sendBlockDone();
					return;
				} else {
					this.sendBlockData();
				}
			} else if (code == 1) {
				this.sendBlockData();
			} else {
				//其他错误
				this.abortTransFile(100, -5, "unknow error");
			}
		} else if (cmd == 0x03) {
			let code = recvdata[7];
			this.isBusySending = 0;
			if (code == 0) {
				//数据正常
				//发送完成，等待回复
				this.increaseSeq();
				this.transFileSuccess();
			} else if (code == 1) {
				//数据错误
				this.abortTransFile(100, -4, "file verify failed");
			}
		} else if (cmd == 0x04) {
			console.log("recv reset ack");
			this.isBusySending = 0;
			let code = recvdata[7];
			if (this.curfileSendCallback != null) {
				this.increaseSeq();
				this.curfileSendCallback(code, 100, "重置成功");
			}
		} else if (cmd == 0x10) {
			//9+4+6 -->id最少4个字节 9+4+4 ->
			if (recvdata.length >= 17) {
				var ipaddr = recvdata[10].toString() + ".";
				ipaddr += recvdata[9].toString() + ".";
				ipaddr += recvdata[8].toString() + ".";
				ipaddr += recvdata[7].toString();
				var devid = "";
				for (var i = 11; i < recvdata.length - 2; i++) {
					devid += recvdata[i].toString(16).padStart(2, '0').toUpperCase();
				}

				const paylaod = {
					ip: ipaddr,
					id: devid,
				};

				// 使用JSON.stringify将对象转换为JSON字符串
				const paylaodString = JSON.stringify(paylaod, null, 2);
				if (this.curfileSendCallback != null) {
					this.curfileSendCallback(0, 100, paylaodString);
				}
			} else {
				console.log("invalid data len:", recvdata.length);
				if (this.curfileSendCallback != null) {
					this.curfileSendCallback(-1, 100, "invalid data len:");
				}
			}
			this.increaseSeq();
			this.isBusySending = 0;
		} else if (cmd == 0x20) {
			let code = recvdata[7];
			console.log("set network response:" + code);
			this.isBusySending = 0;
			if (this.curfileSendCallback != null) {
				this.increaseSeq();
				this.curfileSendCallback(code, 100, "设置网络完成");
			}
		} else if (cmd == 0x30) {
			let code = recvdata[7];
			console.log("correct pos response:" + code);
			this.isBusySending = 0;
			if (this.curfileSendCallback != null) {
				this.increaseSeq();
				if (len >= 17) {
					var tmp = 0;
					var idx = 8;
					const rsppos = {};
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.x1 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.y1 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.x2 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.y2 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.x3 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.y3 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.x4 = tmp;
					tmp = recvdata[idx++];
					tmp <<= 8;
					tmp += recvdata[idx++];
					rsppos.y4 = tmp;
					const paylaodString = JSON.stringify(rsppos, null, 2);
					this.curfileSendCallback(code, 100, paylaodString);
				} else {
					this.curfileSendCallback(code, 100, "ok");
				}
			}
		}
	}

	resetFileTransfer(cb) {
		this.blockSendTimes = 0;
		this.curfileSendCallback = cb;
		this.sendResetFile(cb);
	}

	sendResetFile(cb) {
		if (this.blockResendCheck()) {
			return;
		}
		let seq = this.curSendSeq;
		let senddata = [0x55, 0xAA, 0x00, 0x00, 0xFF & (seq >> 8), 0xFF & seq, 0x04];
		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);

		console.log('senddata before sending:', senddata);
		this.sendfunc(senddata);

		this.blcokTimeoutTimr = setTimeout(() => {
			this.sendResetFile();
		}, this.blcokResendTimeout);
	}
	//发送完成的文件过去
	sendBinaryFile(data, format, cb) {
		//console.log("xxxxxxxxxxxxx");
		//如果当前还有数据没有发送完，就报错
		if (this.senddataOffset > 0) {
			throw new Error("send data not finished");
		}

		//format只支持jpg格式，传入的fmt是字符串格式，我要判断下
		if (format != "jpg") {
			throw new Error("not support format");
		}

		this.blockSendTimes = 0;
		this.senddataBuf = new Uint8Array(data);
		this.senddataOffset = 0;
		this.curfileSendCallback = cb;
		this.sendBlockStart();
	}

	abortTransFile(code, step, msg) {
		if (this.curfileSendCallback != null) {
			this.curfileSendCallback(code, step, msg);
		}
		this.curfileSendCallback = null;
		this.senddataOffset = 0;
		this.senddataBuf = [];
		if (this.blcokTimeoutTimr != null) {
			clearTimeout(this.blcokTimeoutTimr);
		}
		this.blcokTimeoutTimr = null;
		this.blockSendTimes = 0;
		this.increaseSeq();
		//发送空闲了
		this.isBusySending = 0;
	}

	transFileSuccess() {
		this.abortTransFile(100, 100, "success");
	}

	blockResendCheck() {
		//判断重发的次数
		if (this.blockSendTimes > this.maxRetryInUnit) {
			//重试次数太多，放弃
			this.abortTransFile(100, -6, "retry too");
			return -1;
		}

		this.blockSendTimes++;
		return 0
	}

	sendBlockDone() {
		if (this.senddataOffset == 0) {
			console.log("send block done, but no data to send");
			return
		}

		let seq = this.curSendSeq;
		//发送完成的数据包
		let senddata = [0x55, 0xAA, 0x00, 0x00, 0xFF & (seq >> 8), 0xFF & seq, 0x03];
		//计算crc，除去最开始和最后的4个字节
		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);
		this.sendfunc(senddata);
		this.transFileSuccess()
		return
	}

	sendBlockStart() {
		// 开始发送标记
		//console.log("totalSize", this.senddataBuf.byteLength);
		if (this.senddataOffset > 0) {
			console.log("already in sending state");
			return;
		}
		if (this.blockResendCheck()) {
			return;
		}

		if (!this.senddataBuf || this.senddataBuf.byteLength === 0) {
			console.error('senddataBuf is not initialized or is empty');
			return;
		}

		if (this.curfileFormat < 0 || this.curfileFormat > 255) {
			console.error('curfileFormat is out of range');
			return;
		}

		let seq = this.curSendSeq;
		let senddata = [0x55, 0xAA, 0x00, 0x04, 0xFF & (seq >> 8), 0xFF & seq, 0x01];
		senddata.push(this.curfileFormat);
		senddata.push(this.senddataBuf.byteLength >> 16);
		senddata.push((this.senddataBuf.byteLength >> 8) & 0xFF);
		senddata.push(this.senddataBuf.byteLength & 0xFF);

		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);

		//console.log('senddata before sending:', senddata);
		this.sendfunc(senddata);

		this.blcokTimeoutTimr = setTimeout(() => {
			this.sendBlockStart();
		}, this.blcokResendTimeout);
	}

	sendCmdGetDevInfo(cmd) {
		if (this.blockResendCheck()) {
			return;
		}
		let seq = this.curSendSeq;
		let senddata = [0x55, 0xAA, 0x00, 0x00, 0xFF & (seq >> 8), 0xFF & seq, cmd];
		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);
		this.sendfunc(senddata);

		this.blcokTimeoutTimr = setTimeout(() => {
			this.sendCmdGetDevInfo(cmd);
		}, this.blcokResendTimeout);
	}

	sendCmdSetOption(opt, value) {
		if(this.blockResendCheck()){
		   return;
	   }
	   let seq = this.curSendSeq;
	   let senddata = [0x55, 0xAA, 0x00, 0x03, 0xFF & (seq >> 8), 0xFF & seq, 0x40];
	   senddata.push(opt);
	   senddata.push(value >> 16);
	   senddata.push((value >> 8) & 0xFF);

	   let crc = this.crc16(senddata.slice(2, senddata.byteLength));
	   senddata.push(crc >> 8);
	   senddata.push(crc & 0xFF);
	   this.sendfunc(senddata);

	   this.blcokTimeoutTimr = setTimeout(() => {
		 this.sendCmdSetOption(opt, value);
	   }, this.blcokResendTimeout);
	 }

	sendCmdDevNetwork(ssid, password) {

		if (this.blockResendCheck()) {
			return;
		}
		//添加下一波数据
		const paylaod = {
			s: ssid,
			p: password,
		};


		let blockdata = new Uint8Array(0);
		const paylaodString = JSON.stringify(paylaod, null, 2);

		console.log("netstring:" + paylaodString.length, " body:", paylaodString);

		console.log("network info length:" + blockdata.length);

		let payloadlen = paylaodString.length;
		let seq = this.curSendSeq;
		let senddata = [0x55, 0xAA, 0xFF & (payloadlen >> 8), 0xFF & (payloadlen), 0xFF & (seq >> 8), 0xFF & seq,
			0x20
		];
		//Array.prototype.push.apply(senddata, blockdata);
		for (let i = 0; i < payloadlen; i++) {
			//uint8Array[i] = paylaodString.charCodeAt(i);
			//blockdata.length++;
			//blockdata[blockdata.length-1]=paylaodString.charCodeAt(i);
			senddata.push(paylaodString.charCodeAt(i));
		}

		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);
		this.sendfunc(senddata);

		this.blcokTimeoutTimr = setTimeout(() => {
			this.sendCmdDevNetwork(ssid, password);
		}, this.blcokResendTimeout);
	}

	GetDevInfo(cb) {
		if (this.isBusySending == 1) {
			console.log("sending is busy...")
			return
		}

		//标记正在发送数据
		this.isBusySending = 1;
		this.blockSendTimes = 0;
		this.curfileSendCallback = cb;
		this.sendCmdGetDevInfo(0x10);
	}
	
	SetOptions(opt, val, cb) {
	   if (this.isBusySending == 1){
	       console.log("sending is busy...")
	       return
	   }
	   //标记正在发送数据
	   this.isBusySending = 1;
	   this.blockSendTimes = 0;
	   this.curfileSendCallback = cb;
	   this.sendCmdSetOption(opt, val);
	}

	SetDevNetwork(ssid, password, cb) {

		if (this.isBusySending == 1) {
			console.log("sending is busy...")
			return
		}

		//标记正在发送数据
		this.isBusySending = 1;
		this.blockSendTimes = 0;
		this.curfileSendCallback = cb;
		this.sendCmdDevNetwork(ssid, password);
	}

	sendBlockData() {
		if (!this.senddataBuf || this.senddataOffset >= this.senddataBuf.byteLength) {
			console.error("senddataBuf is undefined or senddataOffset exceeds the buffer length");
			return;
		}
		if (this.blockResendCheck()) {
			return;
		}
		let actsize = this.sendBlockSize;
		if (actsize + this.senddataOffset > this.senddataBuf.byteLength) {
			actsize = this.senddataBuf.byteLength - this.senddataOffset;
		}

		if (actsize <= 0) {
			console.error("Invalid actsize, no data to send");
			return;
		}

		let seq = this.curSendSeq;
		let blockdata = this.senddataBuf.slice(this.senddataOffset, this.senddataOffset + actsize);
		if (!blockdata || blockdata.length === 0) {
			console.error("Block data is empty or not properly sliced");
			return;
		}

		//console.log("sending offset:", this.senddataOffset);
		let frmIdx = this.senddataOffset / this.sendBlockSize;
		let senddata = [0x55, 0xAA, 0xFF & ((actsize + 2) >> 8), 0xFF & (actsize + 2), 0xFF & (seq >> 8), 0xFF &
			seq, 0x02
		];
		senddata.push(frmIdx >> 8);
		senddata.push(frmIdx & 0xFF);
		Array.prototype.push.apply(senddata, blockdata);
		//senddata = senddata.concat(blockdata);
		let crc = this.crc16(senddata.slice(2, senddata.length));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);
		this.sendfunc(senddata);
		//console.log('senddata before sending:', senddata);
		// 开启响应定时器
		this.blcokTimeoutTimr = setTimeout(() => {
			// 直接重发, 成功了会自动清除掉这个定时器
			this.sendBlockData();
		}, this.blcokResendTimeout);
	}

	sendCmdCorrectPos(state, x1, y1, x2, y2, x3, y3, x4, y4) {

		if (this.blockResendCheck()) {
			return;
		}

		let seq = this.curSendSeq;
		let senddata = [0x55, 0xAA, 0x00, 17, 0xFF & (seq >> 8), 0xFF & seq, 0x30];
		//Array.prototype.push.apply(senddata, blockdata);
		senddata.push(state);
		senddata.push((x1 >> 8) & 0xFF);
		senddata.push(x1 & 0xFF);
		senddata.push((y1 >> 8) & 0xFF);
		senddata.push(y1 & 0xFF);
		senddata.push((x2 >> 8) & 0xFF);
		senddata.push(x2 & 0xFF);
		senddata.push((y2 >> 8) & 0xFF);
		senddata.push(y2 & 0xFF);
		senddata.push((x3 >> 8) & 0xFF);
		senddata.push(x3 & 0xFF);
		senddata.push((y3 >> 8) & 0xFF);
		senddata.push(y3 & 0xFF);
		senddata.push((x4 >> 8) & 0xFF);
		senddata.push(x4 & 0xFF);
		senddata.push((y4 >> 8) & 0xFF);
		senddata.push(y4 & 0xFF);

		let crc = this.crc16(senddata.slice(2, senddata.byteLength));
		senddata.push(crc >> 8);
		senddata.push(crc & 0xFF);
		this.sendfunc(senddata);

		this.blcokTimeoutTimr = setTimeout(() => {
			this.sendCmdCorrectPos(state, x1, y1, x2, y2, x3, y3, x4, y4);
		}, this.blcokResendTimeout);
	}

	SetCorrectPos(state, x1, y1, x2, y2, x3, y3, x4, y4, cb) {
		if (this.isBusySending == 1) {
			console.log("sending is busy...")
			return
		}

		this.isBusySending = 1;
		this.blockSendTimes = 0;
		this.curfileSendCallback = cb;
		this.sendCmdCorrectPos(state, x1, y1, x2, y2, x3, y3, x4, y4);
	}
}